﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XNAchievement.Achievements;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework.Content;

namespace XNAchievement
{
    /// <summary>
    /// Manages a collection of achievements. Please note that there
    /// is no need to use this class: You can safely use the Achievements
    /// on there own!
    /// </summary>
    public class AchievementManager
    {
        /// <summary>
        /// This event is fired, when any of the achievement is unlocked.
        /// Please note that the "Notification" event will also be
        /// fired. This event is just for simplification of some tasks.
        /// </summary>
        public event EventHandler<AnyUnlockedEventArgs> AnyUnlocked;

        /// <summary>
        /// This event is fired when any of the achievement has reached
        /// a status that might be communicated to the user.
        /// </summary>
        public event EventHandler<AnyNotificationEventArgs> AnyNotification;

        protected Dictionary<string, Achievement> achievements;

        public AchievementManager()
        {
            achievements = new Dictionary<string, Achievement>();
        }

        void a_Notification(object sender, NotificationEventArgs e)
        {
            OnAnyNotification(sender as Achievement, e);
        }

        /// <summary>
        /// Raises the "AnyNotification" event.
        /// </summary>
        protected virtual void OnAnyNotification(Achievement a, NotificationEventArgs e)
        {
            if(AnyNotification != null)
                AnyNotification(this, new AnyNotificationEventArgs(a, e));
        }

        /// <summary>
        /// Raises the "AnyUnlocked" event.
        /// </summary>
        protected virtual void OnAnyUnlocked(Achievement a)
        {
            if (AnyUnlocked != null)
                AnyUnlocked(this, new AnyUnlockedEventArgs(a));
        }

        void a_Unlocked(object sender, EventArgs e)
        {
            OnAnyUnlocked(sender as Achievement);
        }

        /// <summary>
        /// Loads the contetent for all managed achievements.
        /// </summary>
        public virtual void LoadContent(ContentManager content)
        {
            foreach (var a in achievements.Values)
            {
                a.LoadContent(content);
            }
        }

        /// <summary>
        /// Add the given achievement to the list of monitored achievements.
        /// </summary>
        public virtual void Add(Achievement a)
        {
            achievements.Add(a.InternalName, a);
            a.Unlocked += new EventHandler(a_Unlocked);
            a.Notification += new EventHandler<NotificationEventArgs>(a_Notification);
        }

        /// <summary>
        /// Removes the achievement with the given internal name from
        /// the list of monitored achievements.
        /// </summary>
        /// <param name="internalName"></param>
        public virtual void Remove(string internalName)
        {
            achievements[internalName].Unlocked -= a_Unlocked;
            achievements[internalName].Notification -= a_Notification;
            achievements.Remove(internalName);
        }

        /// <summary>
        /// Returns the Achievement with the given internal name.
        /// </summary>
        public virtual Achievement GetByName(string internalName)
        {
            try
            {
                return achievements[internalName];
            }
            catch (KeyNotFoundException)
            {
                throw new Exception(string.Format("A achievement with the internal name {0} was not found", internalName));
            }
        }

        /// <summary>
        /// Returns true when a achievement with the given name is
        /// registered, false otherwise.
        /// </summary>
        public virtual bool HasAchievement(string internalName)
        {
            return achievements.ContainsKey(internalName);
        }

        /// <summary>
        /// Returns the Achievement with the given internal name.
        /// </summary>
        public Achievement this[string internalName]
        {
            get { return achievements[internalName]; }
        }

        /// <summary>
        /// Returns an array with all managed achievements.
        /// </summary>
        public Achievement[] Achievements
        {
            get
            {
                return achievements.Values.ToArray();
            }
        }

        /// <summary>
        /// Returns all achievements that are currenly visible.
        /// That are all achievements that are unlocked or locked
        /// but visible when locked.
        /// </summary>
        public virtual IEnumerable<Achievement> GetVisibleAchievements()
        {
            foreach (var a in achievements.Values)
            {
                if (a.IsLocked && !a.VisibleWhenLocked)
                    continue;
                else
                    yield return a;
            }
        }

        /// <summary>
        /// Writes all achievements (including progress) to the given stream.
        /// </summary>
        public virtual void WriteAllToStream(Stream stream)
        {
            BinaryFormatter formatter = new BinaryFormatter();

            foreach (var a in achievements.Values)
            {
                formatter.Serialize(stream, a);
            }
        }

        /// <summary>
        /// Read all achievements (inlcuding progress) from the given stream
        /// and adds them to this achievement manager.
        /// </summary>
        public virtual void ReadAllFromStream(Stream stream)
        {
            achievements.Clear(); //Remove all or we might have duplicates
            
            BinaryFormatter formatter = new BinaryFormatter();

            while (stream.Position != stream.Length)
            {
                Add((Achievement)formatter.Deserialize(stream));
            }
        }

        /// <summary>
        /// Get a string with all achievements in their string representation
        /// given by the passed IAchievementFormatter. Hidden achievements will be
        /// included when showHidden is set to true.
        /// </summary>
        public virtual string GetAllAsString(IAchievementFormatter formatter, bool showHidden = false)
        {
            StringBuilder sb = new StringBuilder();

            foreach (var a in achievements.Values)
            {
                if (!showHidden && a.IsLocked && !a.VisibleWhenLocked)
                    continue;
                else
                    sb.Append(a.AsString(formatter));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Registeres a INotificationHandler for the "AnyNotification" event.
        /// Please note: This method is just for convinience, you can still register
        /// the event normaly to any method.
        /// </summary>
        public virtual void RegisterNotificationHandler(INotificationHandler handler)
        {
            AnyNotification += handler.HandleAnyNotification;
        }

        /// <summary>
        /// Removes a INotificationHandler for the "AnyNotification" event.
        /// Please note: This method is just for convinience, you can still register
        /// the event normaly to any method.
        /// </summary>
        public virtual void DeRegisterNotificationHandler(INotificationHandler handler)
        {
            AnyNotification -= handler.HandleAnyNotification;
        }
    }
}
